package io.innerloop.neo4j.client.spi.impl.rest; import io.innerloop.neo4j.client.Connection; import io.innerloop.neo4j.client.Graph; import io.innerloop.neo4j.client.GraphStatement; import io.innerloop.neo4j.client.Neo4jClientException; import io.innerloop.neo4j.client.Neo4jServerException; import io.innerloop.neo4j.client.Neo4jServerMultiException; import io.innerloop.neo4j.client.RowSet; import io.innerloop.neo4j.client.RowStatement; import io.innerloop.neo4j.client.Statement; import io.innerloop.neo4j.client.spi.impl.rest.json.JSONException; import io.innerloop.neo4j.client.spi.impl.rest.json.JSONObject; import io.innerloop.neo4j.client.spi.impl.rest.http.HttpClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.time.OffsetDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.stream.Collectors; /** * Created by markangrish on 12/12/2014. */ public class RestConnectionImpl implements Connection { private static final Logger LOG = LoggerFactory.getLogger(RestConnectionImpl.class); private final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss Z").withLocale(Locale.ENGLISH); private static ThreadLocal<RestConnectionImpl> connectionHolder = new ThreadLocal<>(); public static Connection getConnection(HttpClient client, String transactionEndpointUrl) { RestConnectionImpl connection = connectionHolder.get(); if (connection == null) { LOG.debug("Getting new Connection for Thread: [{}]", Thread.currentThread().getName()); connection = new RestConnectionImpl(client, transactionEndpointUrl); connectionHolder.set(connection); } return connection; } public static void closeConnection() { connectionHolder.remove(); } private final HttpClient client; private List<Statement> statements; private String activeTransactionEndpointUrl; private OffsetDateTime transactionExpires; public RestConnectionImpl(HttpClient client, String transactionEndpointUrl) { this.activeTransactionEndpointUrl = transactionEndpointUrl; this.statements = new ArrayList<>(); this.client = client; } @Override public void add(Statement statement) { this.statements.add(statement); } @Override public List<Statement> getStatements() { return statements; } @Override public OffsetDateTime getExpiry() { return transactionExpires; } @Override public void flush() { try { LOG.debug("Flushing to [{}]", activeTransactionEndpointUrl); JSONObject jsonResult = execute(activeTransactionEndpointUrl); this.activeTransactionEndpointUrl = jsonResult.getString("commit").replace("/commit", ""); this.transactionExpires = OffsetDateTime.parse(jsonResult.getJSONObject("transaction").getString("expires"), FORMATTER); this.statements.clear(); LOG.debug("Next endpoint is now: [{}] which expires at: [{}]", activeTransactionEndpointUrl, transactionExpires); } catch (IOException e) { close(); throw new Neo4jClientException(e); } catch (JSONException e) { close(); throw new Neo4jClientException("Error when processing JSON response.", e); } } @Override public void commit() { try { String commitEndpoint = activeTransactionEndpointUrl + "/commit"; LOG.debug("Committing to [{}]", commitEndpoint); execute(commitEndpoint); } catch (Exception e) { throw new Neo4jClientException("Encountered an error when trying to commit to Neo4J. See exception for details.", e); } finally { LOG.debug("Closing connection."); close(); } } @Override public void resetExpiry() { try { final JSONObject payload = new JSONObject().put("statements", new ArrayList<JSONObject>()); LOG.info("Executing [{}] statements.", statements.size()); LOG.debug("Statements are: [{}]", payload.toString()); String result = client.post(activeTransactionEndpointUrl, payload); LOG.debug("Raw result is: [{}]", result); JSONObject jsonResult = new JSONObject(result); ExecutionResult er = new ExecutionResult(jsonResult); checkErrors(er.getErrors()); this.activeTransactionEndpointUrl = jsonResult.getString("commit").replace("/commit", ""); this.transactionExpires = OffsetDateTime.parse(jsonResult.getJSONObject("transaction").getString("expires"), FORMATTER); } catch (Exception e) { close(); throw new Neo4jClientException(e); } } private JSONObject execute(String endpointUrl) throws IOException { List<JSONObject> statements = this.statements.stream().map(Statement::toJson).collect(Collectors.toList()); final JSONObject payload = new JSONObject().put("statements", (statements)); LOG.info("Executing [{}] statements.", statements.size()); LOG.debug("Statements are: [{}]", payload.toString()); String result = client.post(endpointUrl, payload); LOG.debug("Raw result is: [{}]", result); JSONObject jsonResult = new JSONObject(result); ExecutionResult er = new ExecutionResult(jsonResult); checkErrors(er.getErrors()); for (int i = 0; i < this.statements.size(); i++) { Statement statement = this.statements.get(i); JSONObject jsonObject = er.geResult(i); if (statement.getType().equals("row")) { RowSet rs = er.buildRowSet(jsonObject); ((RowStatement) statement).setResult(rs); } else { Graph g = er.buildGraph(jsonObject); ((GraphStatement) statement).setResult(g); } } return jsonResult; } @Override public void rollback() { try { String json = client.delete(activeTransactionEndpointUrl); ExecutionResult er = new ExecutionResult(new JSONObject(json)); checkErrors(er.getErrors()); } catch (Exception e) { close(); throw new Neo4jClientException(e); } } private void close() { closeConnection(); } void checkErrors(Neo4jServerException[] exceptions) { int length = exceptions.length; if (length == 1) { throw exceptions[0]; } if (length > 1) { throw new Neo4jServerMultiException("Multiple errors occurred when executing statements", exceptions); } } }